///////////////////////////////////////////////////////////////////////////////////////////////////
// Illusion Ray Development Kit (IDK)           Copyright (c) 2010 ( dimitriy.martynenko@gmail.com )
///////////////////////////////////////////////////////////////////////////////////////////////////
// Created : 2010-05-22
// Updated : ...
// Licence : NEW BSD
// Info	   : IDK Engine
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma once
/*-------------------------------------------------------------------------
| BasicCamera:
| ------------
| BasicCamera
---------------------------------------------------------------------------*/
class TBasicCamera : public TObject, virtual public IDK::ICamera
{
	IDKClass;
public:
	TBasicCamera(void);
	virtual ~TBasicCamera(void){}
public:
	virtual void LookAt(const Math::Vector3&/* target*/)
	{
	}

	virtual const Math::Vector3 GetAbsolutePosition(void) const
	{
		const Math::Vector4& col = mInvMatrixView[3];
		return Math::Vector3(col.x, col.y, col.z);
	}

	virtual const Math::Vector3& GetPosition(void) const
	{
		return mPosition;
	}
	virtual void SetPosition(const Math::Vector3& pos)
	{
		mPosition = pos;
	}
	virtual const Math::Matrix44& GetMatrix(MatrixMode::Enum mode);
	virtual void Update(float dt);
public:
	virtual void Reset(ref<IDK::IRenderState> rndState)
	{
	}
	virtual bool Draw(ref<IDK::IRenderState> rndState)
	{
		const Math::Matrix44& proj = GetMatrix(IDK::ICamera::MatrixMode::Projection);
		const Math::Matrix44& view = GetMatrix(IDK::ICamera::MatrixMode::View);

		rndState->SetProjMatrix(proj);
		rndState->SetViewMatrix(view);

		//update occluder
		rndState->GetOcclusionQuery()->Update(proj, view);

		//enable culling
		rndState->GetOcclusionQuery()->Enable(true);

		return true;
	}
	virtual void Drawed(ref<IDK::IRenderState> rndState)
	{
		//disable culling
		rndState->GetOcclusionQuery()->Enable(false);
	}

	virtual void SetMoveForce(const Math::Vector3& force)
	{
		mMoveForce = force;
	}
	virtual const Math::Vector3& GetMoveForce()
	{
		return mMoveForce;
	}
	virtual void Rotate(float x, float y);
	virtual const Math::Vector3 ScreenToWorld(const Math::Vector2& posXY) const;
	virtual const Math::Vector3 ScreenToVector(const Math::Vector2& posXY) const;
protected:
	void UpdateHelperMatrices(void);
	const Math::Matrix44& Calculate(MatrixMode::Enum mode);
	void Translate();
protected:
	virtual void CalculateMatrix(IDK::ICamera::MatrixMode::Enum mode, Math::Matrix44& dest);
protected:
	/* mVelocity */
	Math::Vector3 mVelocity;
	Math::Vector3 mMoveForce;
	//4x1 = 4[b]
	float mVelMax;
	float mSpeed;
	/* aspect */
	float mAspect;
	float mAngle;
	//4x1 = 4[b]
	Math::Vector3 mPosition, mForward, mRight;
	// camera matrices
	Math::Matrix44 mMatrixView;
	Math::Matrix44 mMatrixProjection;
	Math::Matrix44 mInvMatrixView;
	Math::Matrix44 mMatrixViewProjection;
	// change matrices
	Set<IDK::ICamera::MatrixMode::Enum> mChangeMatrix;
};

typedef ref<TBasicCamera> BasicCamera;

//
//Implemtation for basic camera TODO: move this into cpp file
//
inline TBasicCamera::TBasicCamera(void)
	: TObject(),
	mVelocity(0.0f), // x - Angular y - Linear,
	mMoveForce(0.0f),
	mVelMax(5.0f),
	mSpeed(11.0f),
	mForward(-0.5f, -0.8f, 1.0f), //0.8
	mPosition(20.0f, 5.0f, 20.0f), //5.0
	mRight(0.0f, 0.0f, 0.0f)
{
	mChangeMatrix.SetMask((unsigned long int)(-1));
	mAspect = 1.3333f;
	
	Rotate(0.0f, 0.0f); //default value
	Translate();
}

inline const Math::Vector3 TBasicCamera::ScreenToWorld(const Math::Vector2& posXY) const
{
	//TEST VALUES
	const Math::Vector2& viewport = Math::Vector2(1024, 768);

	const Math::Vector3 win(posXY.x, viewport.y - 1 - posXY.y, 1.0f);

	const Math::Vector3 near = Math::UnProject(win, mMatrixViewProjection, viewport);
	//const Math::Vector3 far = Math::UnProject(Math::Vector3(posXY, 1.0f), mMatrixViewProjection, viewport);
	//const Math::Vector3 diff = far - near;
	return near;
}

inline const Math::Vector3 TBasicCamera::ScreenToVector(const Math::Vector2& posXY) const
{
	return ScreenToWorld(posXY) - GetAbsolutePosition();
}

inline void TBasicCamera::CalculateMatrix(IDK::ICamera::MatrixMode::Enum mode, Math::Matrix44& dest)
{
	switch(mode)
	{
	case MatrixMode::View :
		dest = Math::LookAt(mPosition, mPosition + mForward, Math::Vector3(0.0f, 1.0f, 0.0f));
		break;
	case MatrixMode::Projection :
		//dest = Math::ortho(-32.0f, 32.0f, -32.0f, 32.0f, -25.0f, 25.0f);
		dest = Math::Perspective(60.0f, mAspect, 0.1f, 1000.0f);
		break;
	default:
		iError("Unkown matrix mode");
	}
}

inline void TBasicCamera::UpdateHelperMatrices(void)
{
	mInvMatrixView = Math::Inverse(mMatrixView);

	mMatrixViewProjection = mMatrixProjection * mMatrixView;
}

inline const Math::Matrix44& TBasicCamera::Calculate(IDK::ICamera::MatrixMode::Enum mode)
{
	switch(mode*mChangeMatrix.In(mode))
	{
	case MatrixMode::View :
		CalculateMatrix(mode, mMatrixView);
		UpdateHelperMatrices();
		break;
	case MatrixMode::Projection :
		CalculateMatrix(mode, mMatrixProjection);
		//UpdateHelperMatrices();
		break;
	}
	//clear state
	mChangeMatrix.Exclude(mode);

	//return 
	switch(mode)
	{
		//ModelView
	case MatrixMode::View:
		return mMatrixView;
		//Projection
	case MatrixMode::Projection:
		return mMatrixProjection;
	}
	iError("Unkown matrix mode");
}

inline const Math::Matrix44& TBasicCamera::GetMatrix(IDK::ICamera::MatrixMode::Enum mode)
{
	return Calculate(mode);
}

inline void TBasicCamera::Rotate(float x, float y)
{
	const Math::Vector3 upY(0.0f, 1.0f, 0.0f);
	mForward = Math::Normalize(mForward);

	mRight = Math::Cross(mForward, upY);
	
	Math::Radian angleX(Math::PI<Math::Vector3::Type>() * (x));

	Math::Quaternion qx = Math::Quaternion(Math::Rotate(upY, angleX));
	mForward = Math::Rotate(qx, mForward);

	Math::Radian angleY(Math::PI<Math::Vector3::Type>() * (y));

	Math::Quaternion qy = Math::Quaternion(Math::Rotate(mRight, angleY));
	mForward = Math::Rotate(qy, mForward);
	
	mChangeMatrix.Include(MatrixMode::View);
}

inline void TBasicCamera::Translate()
{
	/* limit force */
	mVelocity += mMoveForce * mSpeed;
	mVelocity = Math::Clamp(mVelocity *= 0.98f, -mVelMax, mVelMax); // *= 0.98 = 0.02 anti force

	/* add force to object */
	mPosition += Math::Vector3(mForward.x, 0.0f, mForward.z) * mVelocity.x;
	mPosition += mRight	* mVelocity.y;
	mPosition += Math::Vector3(0.0f, 1.0f, 0.0f) * mVelocity.z;
	mChangeMatrix.Include(MatrixMode::View);
}

inline void TBasicCamera::Update(float dt)
{
	//ivec2 lerppos(1, 1);
	//Rotation(Math::Vec2(1.0f,1.0f), dt); //default value
	//Translate(0.0f);
	//Rotation(Vec2((float)lerppos.x, (float)lerppos.y), dt);
	Translate();
}